#!/usr/bin/python
# Copyright (c) 2018, Evert Mulder (base on manageiq_user.py by Daniel Korn <korndaniel1@gmail.com>)
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later

from __future__ import annotations


DOCUMENTATION = r"""
module: manageiq_tenant

short_description: Management of tenants in ManageIQ
extends_documentation_fragment:
  - community.general.manageiq
  - community.general.attributes

author: Evert Mulder (@evertmulder)
description:
  - The manageiq_tenant module supports adding, updating and deleting tenants in ManageIQ.
requirements:
  - manageiq-client
attributes:
  check_mode:
    support: none
  diff_mode:
    support: none
options:
  state:
    type: str
    description:
      - V(absent) - tenant should not exist,
      - V(present) - tenant should be.
    choices: ['absent', 'present']
    default: 'present'
  name:
    type: str
    description:
      - The tenant name.
    required: true
    default:
  description:
    type: str
    description:
      - The tenant description.
    required: true
    default:
  parent_id:
    type: int
    description:
      - The ID of the parent tenant. If not supplied the root tenant is used.
      - The O(parent_id) takes president over O(parent) when supplied.
    default:
  parent:
    type: str
    description:
      - The name of the parent tenant. If not supplied and no O(parent_id) is supplied the root tenant is used.
    default:
  quotas:
    type: dict
    description:
      - The tenant quotas.
      - All parameters case sensitive.
      - 'Valid attributes are:'
      - '- V(cpu_allocated) (int): use null to remove the quota.'
      - '- V(mem_allocated) (GB): use null to remove the quota.'
      - '- V(storage_allocated) (GB): use null to remove the quota.'
      - '- V(vms_allocated) (int): use null to remove the quota.'
      - '- V(templates_allocated) (int): use null to remove the quota.'
    default: {}
"""

EXAMPLES = r"""
- name: Update the root tenant in ManageIQ
  community.general.manageiq_tenant:
    name: 'My Company'
    description: 'My company name'
    manageiq_connection:
      url: 'http://127.0.0.1:3000'
      username: 'admin'
      password: 'smartvm'
      validate_certs: false # only do this when you trust the network!

- name: Create a tenant in ManageIQ
  community.general.manageiq_tenant:
    name: 'Dep1'
    description: 'Manufacturing department'
    parent_id: 1
    manageiq_connection:
      url: 'http://127.0.0.1:3000'
      username: 'admin'
      password: 'smartvm'
      validate_certs: false # only do this when you trust the network!

- name: Delete a tenant in ManageIQ
  community.general.manageiq_tenant:
    state: 'absent'
    name: 'Dep1'
    parent_id: 1
    manageiq_connection:
      url: 'http://127.0.0.1:3000'
      username: 'admin'
      password: 'smartvm'
      validate_certs: false # only do this when you trust the network!

- name: Set tenant quota for cpu_allocated, mem_allocated, remove quota for vms_allocated
  community.general.manageiq_tenant:
    name: 'Dep1'
    parent_id: 1
    quotas:
      - cpu_allocated: 100
      - mem_allocated: 50
      - vms_allocated:
    manageiq_connection:
      url: 'http://127.0.0.1:3000'
      username: 'admin'
      password: 'smartvm'
      validate_certs: false # only do this when you trust the network!


- name: Delete a tenant in ManageIQ using a token
  community.general.manageiq_tenant:
    state: 'absent'
    name: 'Dep1'
    parent_id: 1
    manageiq_connection:
      url: 'http://127.0.0.1:3000'
      token: 'sometoken'
      validate_certs: false # only do this when you trust the network!
"""

RETURN = r"""
tenant:
  description: The tenant.
  returned: success
  type: complex
  contains:
    id:
      description: The tenant ID.
      returned: success
      type: int
    name:
      description: The tenant name.
      returned: success
      type: str
    description:
      description: The tenant description.
      returned: success
      type: str
    parent_id:
      description: The ID of the parent tenant.
      returned: success
      type: int
    quotas:
      description: List of tenant quotas.
      returned: success
      type: list
      sample:
        cpu_allocated: 100
        mem_allocated: 50
"""

from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.manageiq import ManageIQ, manageiq_argument_spec


class ManageIQTenant:
    """
    Object to execute tenant management operations in manageiq.
    """

    def __init__(self, manageiq):
        self.manageiq = manageiq

        self.module = self.manageiq.module
        self.api_url = self.manageiq.api_url
        self.client = self.manageiq.client

    def tenant(self, name, parent_id, parent):
        """Search for tenant object by name and parent_id or parent
            or the root tenant if no parent or parent_id is supplied.
        Returns:
            the parent tenant, None for the root tenant
            the tenant or None if tenant was not found.
        """

        if parent_id:
            parent_tenant_res = self.client.collections.tenants.find_by(id=parent_id)
            if not parent_tenant_res:
                self.module.fail_json(msg=f"Parent tenant with id '{parent_id}' not found in manageiq")
            parent_tenant = parent_tenant_res[0]
            tenants = self.client.collections.tenants.find_by(name=name)

            for tenant in tenants:
                try:
                    ancestry = tenant["ancestry"]
                except AttributeError:
                    ancestry = None

                if ancestry:
                    tenant_parent_id = int(ancestry.split("/")[-1])
                    if int(tenant_parent_id) == parent_id:
                        return parent_tenant, tenant

            return parent_tenant, None
        else:
            if parent:
                parent_tenant_res = self.client.collections.tenants.find_by(name=parent)
                if not parent_tenant_res:
                    self.module.fail_json(msg=f"Parent tenant '{parent}' not found in manageiq")

                if len(parent_tenant_res) > 1:
                    self.module.fail_json(msg=f"Multiple parent tenants not found in manageiq with name '{parent}'")

                parent_tenant = parent_tenant_res[0]
                parent_id = int(parent_tenant["id"])
                tenants = self.client.collections.tenants.find_by(name=name)

                for tenant in tenants:
                    try:
                        ancestry = tenant["ancestry"]
                    except AttributeError:
                        ancestry = None

                    if ancestry:
                        tenant_parent_id = int(ancestry.split("/")[-1])
                        if tenant_parent_id == parent_id:
                            return parent_tenant, tenant

                return parent_tenant, None
            else:
                # No parent or parent id supplied we select the root tenant
                return None, self.client.collections.tenants.find_by(ancestry=None)[0]

    def compare_tenant(self, tenant, name, description):
        """Compare tenant fields with new field values.

        Returns:
            false if tenant fields have some difference from new fields, true o/w.
        """
        found_difference = (name and tenant["name"] != name) or (description and tenant["description"] != description)

        return not found_difference

    def delete_tenant(self, tenant):
        """Deletes a tenant from manageiq.

        Returns:
            dict with `msg` and `changed`
        """
        try:
            url = f"{self.api_url}/tenants/{tenant['id']}"
            result = self.client.post(url, action="delete")
        except Exception as e:
            self.module.fail_json(msg=f"failed to delete tenant {tenant['name']}: {e}")

        if result["success"] is False:
            self.module.fail_json(msg=result["message"])

        return dict(changed=True, msg=result["message"])

    def edit_tenant(self, tenant, name, description):
        """Edit a manageiq tenant.

        Returns:
            dict with `msg` and `changed`
        """
        resource = dict(name=name, description=description, use_config_for_attributes=False)

        # check if we need to update ( compare_tenant is true is no difference found )
        if self.compare_tenant(tenant, name, description):
            return dict(changed=False, msg=f"tenant {tenant['name']} is not changed.", tenant=tenant["_data"])

        # try to update tenant
        try:
            self.client.post(tenant["href"], action="edit", resource=resource)
        except Exception as e:
            self.module.fail_json(msg=f"failed to update tenant {tenant['name']}: {e}")

        return dict(changed=True, msg=f"successfully updated the tenant with id {tenant['id']}")

    def create_tenant(self, name, description, parent_tenant):
        """Creates the tenant in manageiq.

        Returns:
            dict with `msg`, `changed` and `tenant_id`
        """
        parent_id = parent_tenant["id"]
        # check for required arguments
        for key, value in dict(name=name, description=description, parent_id=parent_id).items():
            if value in (None, ""):
                self.module.fail_json(msg=f"missing required argument: {key}")

        url = f"{self.api_url}/tenants"

        resource = {"name": name, "description": description, "parent": {"id": parent_id}}

        try:
            result = self.client.post(url, action="create", resource=resource)
            tenant_id = result["results"][0]["id"]
        except Exception as e:
            self.module.fail_json(msg=f"failed to create tenant {name}: {e}")

        return dict(
            changed=True, msg=f"successfully created tenant '{name}' with id '{tenant_id}'", tenant_id=tenant_id
        )

    def tenant_quota(self, tenant, quota_key):
        """Search for tenant quota object by tenant and quota_key.
        Returns:
            the quota for the tenant, or None if the tenant quota was not found.
        """

        tenant_quotas = self.client.get(f"{tenant['href']}/quotas?expand=resources&filter[]=name={quota_key}")

        return tenant_quotas["resources"]

    def tenant_quotas(self, tenant):
        """Search for tenant quotas object by tenant.
        Returns:
            the quotas for the tenant, or None if no tenant quotas were not found.
        """

        tenant_quotas = self.client.get(f"{tenant['href']}/quotas?expand=resources")

        return tenant_quotas["resources"]

    def update_tenant_quotas(self, tenant, quotas):
        """Creates the tenant quotas in manageiq.

        Returns:
            dict with `msg` and `changed`
        """

        changed = False
        messages = []
        for quota_key, quota_value in quotas.items():
            current_quota_filtered = self.tenant_quota(tenant, quota_key)
            if current_quota_filtered:
                current_quota = current_quota_filtered[0]
            else:
                current_quota = None

            if quota_value:
                # Change the byte values to GB
                if quota_key in ["storage_allocated", "mem_allocated"]:
                    quota_value_int = int(quota_value) * 1024 * 1024 * 1024
                else:
                    quota_value_int = int(quota_value)
                if current_quota:
                    res = self.edit_tenant_quota(tenant, current_quota, quota_key, quota_value_int)
                else:
                    res = self.create_tenant_quota(tenant, quota_key, quota_value_int)
            else:
                if current_quota:
                    res = self.delete_tenant_quota(tenant, current_quota)
                else:
                    res = dict(changed=False, msg=f"tenant quota '{quota_key}' does not exist")

            if res["changed"]:
                changed = True

            messages.append(res["msg"])

        return dict(changed=changed, msg=", ".join(messages))

    def edit_tenant_quota(self, tenant, current_quota, quota_key, quota_value):
        """Update the tenant quotas in manageiq.

        Returns:
            result
        """

        if current_quota["value"] == quota_value:
            return dict(changed=False, msg=f"tenant quota {quota_key} already has value {quota_value}")
        else:
            url = f"{tenant['href']}/quotas/{current_quota['id']}"
            resource = {"value": quota_value}
            try:
                self.client.post(url, action="edit", resource=resource)
            except Exception as e:
                self.module.fail_json(msg=f"failed to update tenant quota {quota_key}: {e}")

            return dict(changed=True, msg=f"successfully updated tenant quota {quota_key}")

    def create_tenant_quota(self, tenant, quota_key, quota_value):
        """Creates the tenant quotas in manageiq.

        Returns:
            result
        """
        url = f"{tenant['href']}/quotas"
        resource = {"name": quota_key, "value": quota_value}
        try:
            self.client.post(url, action="create", resource=resource)
        except Exception as e:
            self.module.fail_json(msg=f"failed to create tenant quota {quota_key}: {e}")

        return dict(changed=True, msg=f"successfully created tenant quota {quota_key}")

    def delete_tenant_quota(self, tenant, quota):
        """deletes the tenant quotas in manageiq.

        Returns:
            result
        """
        try:
            result = self.client.post(quota["href"], action="delete")
        except Exception as e:
            self.module.fail_json(msg=f"failed to delete tenant quota '{quota['name']}': {e}")

        return dict(changed=True, msg=result["message"])

    def create_tenant_response(self, tenant, parent_tenant):
        """Creates the ansible result object from a manageiq tenant entity

        Returns:
            a dict with the tenant id, name, description, parent id,
            quota's
        """
        tenant_quotas = self.create_tenant_quotas_response(tenant["tenant_quotas"])

        try:
            ancestry = tenant["ancestry"]
            tenant_parent_id = ancestry.split("/")[-1]
        except AttributeError:
            # The root tenant does not return the ancestry attribute
            tenant_parent_id = None

        return dict(
            id=tenant["id"],
            name=tenant["name"],
            description=tenant["description"],
            parent_id=tenant_parent_id,
            quotas=tenant_quotas,
        )

    @staticmethod
    def create_tenant_quotas_response(tenant_quotas):
        """Creates the ansible result object from a manageiq tenant_quotas entity

        Returns:
            a dict with the applied quotas, name and value
        """

        if not tenant_quotas:
            return {}

        result = {}
        for quota in tenant_quotas:
            if quota["unit"] == "bytes":
                value = float(quota["value"]) / (1024 * 1024 * 1024)
            else:
                value = quota["value"]
            result[quota["name"]] = value
        return result


def main():
    argument_spec = dict(
        name=dict(required=True, type="str"),
        description=dict(required=True, type="str"),
        parent_id=dict(type="int"),
        parent=dict(type="str"),
        state=dict(choices=["absent", "present"], default="present"),
        quotas=dict(type="dict", default={}),
    )
    # add the manageiq connection arguments to the arguments
    argument_spec.update(manageiq_argument_spec())

    module = AnsibleModule(argument_spec=argument_spec)

    name = module.params["name"]
    description = module.params["description"]
    parent_id = module.params["parent_id"]
    parent = module.params["parent"]
    state = module.params["state"]
    quotas = module.params["quotas"]

    manageiq = ManageIQ(module)
    manageiq_tenant = ManageIQTenant(manageiq)

    parent_tenant, tenant = manageiq_tenant.tenant(name, parent_id, parent)

    # tenant should not exist
    if state == "absent":
        # if we have a tenant, delete it
        if tenant:
            res_args = manageiq_tenant.delete_tenant(tenant)
        # if we do not have a tenant, nothing to do
        else:
            if parent_id:
                msg = f"tenant '{name}' with parent_id {int(parent_id)} does not exist in manageiq"
            else:
                msg = f"tenant '{name}' with parent '{parent}' does not exist in manageiq"

            res_args = dict(changed=False, msg=msg)

    # tenant should exist
    if state == "present":
        # if we have a tenant, edit it
        if tenant:
            res_args = manageiq_tenant.edit_tenant(tenant, name, description)

        # if we do not have a tenant, create it
        else:
            res_args = manageiq_tenant.create_tenant(name, description, parent_tenant)
            tenant = manageiq.client.get_entity("tenants", res_args["tenant_id"])

        # quotas as supplied and we have a tenant
        if quotas:
            tenant_quotas_res = manageiq_tenant.update_tenant_quotas(tenant, quotas)
            if tenant_quotas_res["changed"]:
                res_args["changed"] = True
                res_args["tenant_quotas_msg"] = tenant_quotas_res["msg"]

        tenant.reload(expand="resources", attributes=["tenant_quotas"])
        res_args["tenant"] = manageiq_tenant.create_tenant_response(tenant, parent_tenant)

    module.exit_json(**res_args)


if __name__ == "__main__":
    main()
